﻿
using System;

namespace CatEars.Core.Define
{
    /// <summary>
    /// 农历日期类
    /// </summary>
    public sealed class LunarDateTime : System.Globalization.ChineseLunisolarCalendar
    {
        #region 农历结构体
        /// <summary>
        /// 农历结构体
        /// </summary>
        private struct LunarDateTimeStruct
        {
            /// <summary>
            /// 当前日期的农历年
            /// </summary>
            public int m_intYear;

            /// <summary>
            /// 当前日期的农历月份
            /// </summary>
            public int m_intMonth;//1-13

            /// <summary>
            /// 当前日期的农历月中天数
            /// </summary>
            public int m_intDay;//1-30

            /// <summary>
            /// 当前年闰月所在月份
            /// </summary>
            public int m_intLeapNum;
        }
        #endregion

        #region 基础数据数组
        /// <summary>
        /// 节气名称
        /// </summary>
        public static string[] JieQi = { "小寒", "大寒", "立春", "雨水", "惊蛰", "春分",
                                           "清明", "谷雨", "立夏", "小满", "芒种", "夏至",
                                           "小暑", "大暑", "立秋", "处暑", "白露", "秋分",
                                           "寒露", "霜降", "立冬", "小雪", "大雪", "冬至" };
        /// <summary>
        /// 节气时间量 用于计算 单位毫秒
        /// </summary>
        private static double[] JieQiNum = { 0, 21208, 42467, 63836, 85337, 107014,
                                           128867, 150921, 173149, 195551, 218072, 240693,
                                           263343, 285989, 308563, 331033, 353350, 375494,
                                           397447, 419210, 440795, 462224, 483532, 504758 };

        /// <summary>
        /// 10天干名称
        /// </summary>
        public static string[] TianGan = { "甲", "乙", "丙", "丁","戊",
                                             "己", "庚", "辛", "壬", "癸" };

        /// <summary>
        /// 12地支名称
        /// </summary>
        public static string[] DiZhi = { "子", "丑", "寅", "卯",
                                           "辰", "巳", "午", "未",
                                           "申", "酉", "戌", "亥" };

        /// <summary>
        /// 十二生肖名称
        /// </summary>
        public static string[] ShengXiao = { "鼠", "牛", "虎", "兔",
                                               "龙", "蛇", "马", "羊",
                                               "猴", "鸡", "狗", "猪" };

        /// <summary>
        /// 十二星座在Index月的起始日
        /// </summary>
        public static int[] XingZuoDay = { 21, 20, 21, 21,
                                             22, 22, 23, 23,
                                             24, 24, 23, 22 };
        /// <summary>
        /// 十二星座中文名称
        /// </summary>
        public static string[] XingZuoCH = { "水瓶座", "双鱼座", "白羊座", "金牛座",
                                               "双子座", "巨蟹座", "狮子座", "处女座",
                                               "天秤座", "天蝎座", "射手座", "摩羯座", };
        /// <summary>
        /// 十二星座英文名称
        /// </summary>
        public static string[] XingZuoEN = { "Aquarius", "Pisces", "Aries", "Taurus",
                                               "Gemini", "Cancer", "Leo", "Virgo",
                                               "Libra", "Scorpio", "Sagittarius", "Capricorn" };

        /// <summary>
        /// 农历日期
        /// </summary>
        public static string[] DayName = {"*","初一","初二","初三","初四","初五",
                                              "初六","初七","初八","初九","初十",
                                              "十一","十二","十三","十四","十五",
                                              "十六","十七","十八","十九","二十",
                                              "廿一","廿二","廿三","廿四","廿五",
                                              "廿六","廿七","廿八","廿九","三十"};

        /// <summary>
        /// 农历月份
        /// </summary>
        public static string[] MonthName = { "*", "正", "二", "三", "四", "五", "六",
                                               "七", "八", "九", "十", "辜", "腊" };
        #endregion

        #region 变量 属性
        /// <summary>
        /// 公历日期
        /// </summary>
        private DateTime m_dteGregorian;
        /// <summary>
        /// 农历日期
        /// </summary>
        private LunarDateTimeStruct m_dtelunar;

        /// <summary>
        /// 获取公历日期
        /// </summary>
        public DateTime Gregorian
        {
            get { return m_dteGregorian; }
        }

        /// <summary>
        /// 获取当前日期的农历年
        /// </summary>
        public int Year
        {
            get { return m_dtelunar.m_intYear; }
        }

        /// <summary>
        /// 获取当前日期的农历月份 1-13
        /// 一年可能有13个月 1个闰月
        /// </summary>
        public int Month
        {
            get { return m_dtelunar.m_intMonth; }
        }

        /// <summary>
        /// 获取当前日期的农历月中天数 1-30
        /// </summary>
        public int Day
        {
            get { return m_dtelunar.m_intDay; }
        }

        /// <summary>
        /// 获取当前年闰月所在月份 0表示当年没有闰月
        /// </summary>
        public int LeapNum
        {
            get { return m_dtelunar.m_intLeapNum; }
        }

        #endregion

        #region 转换

        /// <summary>
        /// 获取指定公历时间转换为农历时间
        /// </summary>
        /// <param name="dateTime">公历时间</param>
        /// <returns>农历时间</returns>
        public static LunarDateTime ToChinaDateTime(DateTime dateTime)
        {
            return new LunarDateTime(dateTime);
        }

        /// <summary>
        /// 获取指定农历时间对应的公历时间
        /// </summary>
        /// <param name="lunarTime">农历时间</param>
        /// <returns>公历时间</returns>
        public static DateTime ToDateTime(LunarDateTime lunarTime)
        {
            return lunarTime.ToDateTime();
        }

        /// <summary>
        /// 获取当前农历日期的公历时间
        /// </summary>
        /// <returns>DateTime对象</returns>
        public DateTime ToDateTime()
        {
            return this.ToDateTime(
                m_dtelunar.m_intYear,
                m_dtelunar.m_intLeapNum,
                m_dtelunar.m_intDay,
                m_dteGregorian.Hour,
                m_dteGregorian.Minute,
                m_dteGregorian.Second,
                m_dteGregorian.Millisecond);
        }
        #endregion

        #region 构造函数
        /// <summary>
        /// 获取LunarDateTime对象 表示当前时间
        /// </summary>
        public LunarDateTime()
            : this(DateTime.Now)
        {
        }

        /// <summary>
        /// 获取LunarDateTime对象 指定DataTime对象
        /// </summary>
        /// <param name="gregorian">时间</param>
        public LunarDateTime(DateTime gregorian)
        {
            SetLunarDateTime(
                true,
                gregorian.Year, gregorian.Month, gregorian.Day,
                gregorian.Hour, gregorian.Minute, gregorian.Second, gregorian.Millisecond,
                DateTimeKind.Local);
        }

        /// <summary>
        /// 获取LunarDateTime对象 指定年月日时分秒
        /// </summary>
        /// <param name="type">true 公历 false 农历</param>
        /// <param name="year">年</param>
        /// <param name="month">月</param>
        /// <param name="day">日</param>
        /// <param name="hour">时</param>
        /// <param name="minute">分</param>
        /// <param name="second">秒</param>
        /// <param name="millisecond">毫秒</param>
        /// <param name="dateTimeKind">dateTimeKind枚举</param>
        public LunarDateTime(bool type,
            int year,
            int month,
            int day,
            int hour = 0,
            int minute = 0,
            int second = 0,
            int millisecond = 0,
            DateTimeKind dateTimeKind = DateTimeKind.Local)
        {
            SetLunarDateTime(
                type,
                year, month, day,
                hour, minute, second, millisecond,
                dateTimeKind);
        }
        #endregion

        #region 日期运算
        /// <summary>
        /// 初始化LunarDateTime
        /// </summary>
        /// <param name="type">true 公历 false 农历</param>
        /// <param name="year">年</param>
        /// <param name="month">月</param>
        /// <param name="day">日</param>
        /// <param name="hour">时</param>
        /// <param name="minute">分</param>
        /// <param name="second">秒</param>
        /// <param name="millisecond">毫秒</param>
        /// <param name="dateTimeKind">dateTimeKind枚举</param>
        public void SetLunarDateTime(
            bool type,
            int year,
            int month,
            int day,
            int hour = 0,
            int minute = 0,
            int second = 0,
            int millisecond = 0,
            DateTimeKind dateTimeKind = DateTimeKind.Local)
        {
            if (type)//参数为公历日期
            {
                this.m_dteGregorian = new DateTime(
                    year, month, day,
                    hour, minute, second, millisecond,
                    dateTimeKind);
                if (this.m_dteGregorian > this.MaxSupportedDateTime
                    || this.m_dteGregorian < this.MinSupportedDateTime)
                {
                    throw new ArgumentOutOfRangeException(
                        string.Format("参数日期时间不在支持的范围内,支持范围：{0}到{1}",
                        this.MinSupportedDateTime.ToShortDateString(),
                        this.MaxSupportedDateTime.ToShortDateString()));
                }

                m_dtelunar.m_intYear = this.GetYear(this.m_dteGregorian);
                m_dtelunar.m_intMonth = this.GetMonth(this.m_dteGregorian);
                m_dtelunar.m_intDay = this.GetDayOfMonth(this.m_dteGregorian);
                m_dtelunar.m_intLeapNum = this.GetLeapMonth(m_dtelunar.m_intYear);
            }
            else//参数为农历日期
            {
                if (year >= this.MaxSupportedDateTime.Year
                    || year < this.MinSupportedDateTime.Year)
                {
                    throw new ArgumentOutOfRangeException(
                        string.Format("参数年份时间不在支持的范围内,支持范围：{0}到{1}",
                        this.MinSupportedDateTime.ToShortDateString(),
                        this.MaxSupportedDateTime.ToShortDateString()));
                }

                int intMaxMonth = 12;
                if (this.GetLeapMonth(year) != 0) intMaxMonth = 13;
                if (month < 1 || month > intMaxMonth)
                {
                    throw new ArgumentOutOfRangeException("月份超出范围");
                }

                if (this.GetDaysInMonth(year, month) < day || day < 1)
                {
                    throw new ArgumentOutOfRangeException(
                        "指定的月中的天数不在当前月天数有效范围");
                }

                m_dtelunar.m_intYear = year;
                m_dtelunar.m_intMonth = month;
                m_dtelunar.m_intDay = day;
                m_dtelunar.m_intLeapNum = this.GetLeapMonth(year);

                //获取当前农历日期大年初一所对应的公历日期
                DateTime firstDay = GetFirstDay(m_dtelunar.m_intYear);
                for (int i = 1; i < m_dtelunar.m_intMonth; i++)
                {
                    //加上每个农历月的天数
                    firstDay = firstDay.AddDays(GetDaysInMonth(m_dtelunar.m_intYear, i));
                }
                //再加上当前月农历日数，得到农历对应的公历日期
                m_dteGregorian = firstDay.AddDays(m_dtelunar.m_intDay - 1);
            }
        }

        /// <summary>
        /// 获取当前农历日期大年初一所对应的公历日期
        /// </summary>
        /// <param name="year">要获取的年份</param>
        /// <returns>日期</returns>
        private DateTime GetFirstDay(int year)
        {
            DateTime dteDateTime = new DateTime(year, 1, 1);
            if (year == MinSupportedDateTime.Year)
            {
                dteDateTime = MinSupportedDateTime;
            }
            else
            {
                for (int i = 0; i < 1000; i++)
                {
                    //1、通过选定一个公历日期，转换成对应的农历日期
                    //2、判断此农历日期与目标农历日期(大年初一)的日期差值
                    //3、调整公历日期，重复1，2
                    LunarDateTime dteLunarDateTime = new LunarDateTime(dteDateTime);
                    if (dteLunarDateTime.m_dtelunar.m_intYear == year)
                    {
                        if (dteLunarDateTime.m_dtelunar.m_intMonth == 1)
                        {
                            dteDateTime = dteDateTime.AddDays(1 - dteLunarDateTime.m_dtelunar.m_intDay);
                            break;
                        }
                        else
                        {
                            dteDateTime = dteDateTime.AddDays(-15);
                        }
                    }
                    else if (dteLunarDateTime.m_dtelunar.m_intYear < year)
                    {
                        dteDateTime = dteDateTime.AddMonths(1);
                    }
                    else
                    {
                        dteDateTime = dteDateTime.AddMonths(-6);
                    }
                    //防止无限循环
                    if (i == 999) throw new Exception("运算错误");
                }
            }
            return dteDateTime;
        }

        #endregion

        #region 节气
        /// <summary>
        /// 获取当前节气名称
        /// </summary>
        /// <returns>节气名称 当天不是节气当天的 返回前后2个节气名称，用,隔开</returns>
        public string GetJieQi()
        {
            DateTime JieQiDateTime = new DateTime();
            int i = 0;
            for (; i < 24; i++)
            {
                JieQiDateTime = GetJieQi(Gregorian.Year, i);
                if (JieQiDateTime.Year == Gregorian.Year
                    && JieQiDateTime.Month == Gregorian.Month
                    && JieQiDateTime.Day == Gregorian.Day)
                {
                    return JieQi[i];
                }
                else if (JieQiDateTime > Gregorian)
                {
                    break;
                }
            }
            if (i == 24)
            {
                return JieQi[23] + "," + JieQi[0];
            }
            else
            {
                return JieQi[(i + 24 - 1) % 24] + "," + JieQi[i];
            }
        }

        /// <summary>
        /// 根据年份节气名称获取当年节气日期
        /// </summary>
        /// <param name="year">年份</param>
        /// <param name="jieqiName">节气名称</param>
        /// <returns>日期</returns>
        public static DateTime GetJieQi(int year, string jieqiName)
        {
            for (int i = 0; i < JieQi.Length; i++)
            {
                if (JieQi[i] == jieqiName)
                {
                    return GetJieQi(year, i);
                }
            }
            throw new ArgumentException("非法的节气名称");
        }

        /// <summary>
        /// 根据年份及节气Index获取日期
        /// </summary>
        /// <param name="year">年份</param>
        /// <param name="index">节气Index</param>
        /// <returns>日期</returns>
        public static DateTime GetJieQi(int year, int index)
        {
            if (index >= JieQi.Length)
            {
                throw new ArgumentOutOfRangeException(
                    "节气Index超出范围(0-" + JieQi.Length + ")");
            }
            DateTime Start = new DateTime(1900, 1, 6, 2, 5, 0);
            double Num = 0;
            if (year == 2009 && index == 2)
            {
                Num = 43467;
            }
            else
            {
                Num = JieQiNum[index];
            }
            Start = Start.AddMilliseconds(31556925974.7 * (year - 1900));
            Start = Start.AddMilliseconds(Num * 60000);
            return Start;
        }
        #endregion

        #region 生肖
        /// <summary>
        /// 获取生肖名称
        /// </summary>
        /// <returns>生肖名称</returns>
        public string GetLunarYearShengXiao()
        {
            return GetLunarYearShengXiao(m_dtelunar.m_intYear);
        }
        /// <summary>
        /// 获取指定年份的生肖名称
        /// </summary>
        /// <param name="year">年份</param>
        /// <returns>生肖名称</returns>
        public static string GetLunarYearShengXiao(int year)
        {
            return ShengXiao[(year - 4) % 60 % 12];
        }
        #endregion

        #region 年柱
        /// <summary>
        /// 获取年柱天干
        /// </summary>
        /// <returns>年柱天干名称</returns>
        public string GetLunarYearTianGan()
        {
            return GetLunarYearTianGan(m_dtelunar.m_intYear);
        }
        /// <summary>
        /// 获取指定年份的年柱天干
        /// </summary>
        /// <param name="year">指定年份</param>
        /// <returns>年柱天干名称</returns>
        public static string GetLunarYearTianGan(int year)
        {
            return TianGan[(year - 4) % 60 % 10];
        }

        /// <summary>
        /// 获取年柱地支
        /// </summary>
        /// <returns>年柱地支名称</returns>
        public string GetLunarYearDiZhi()
        {
            return GetLunarYearDiZhi(m_dtelunar.m_intYear);
        }
        /// <summary>
        /// 获取指定年份的年柱地支
        /// </summary>
        /// <param name="year">指定年份</param>
        /// <returns>年柱 (天干)名称</returns>
        public static string GetLunarYearDiZhi(int year)
        {
            return DiZhi[(year - 4) % 60 % 12];
        }
        #endregion

        #region 月柱 没有静态方法
        /// <summary>
        /// 获取月柱天干
        /// </summary>
        /// <returns>月柱天干名称</returns>
        public string GetLunarMonthTianGan()
        {
            return TianGan[GetMonthNum() % 10];
        }

        /// <summary>
        /// 获取月柱地支
        /// </summary>
        /// <returns>月柱地支名称</returns>
        public string GetLunarMonthDiZhi()
        {
            return DiZhi[GetMonthNum() % 12];
        }

        /// <summary>
        /// 月柱天干地支计算方法
        /// </summary>
        /// <returns>月份序号 0-60</returns>
        private int GetMonthNum()
        {
            int intNumInYear = 0;
            DateTime dteJieQiDateTime = new DateTime();
            for (; intNumInYear < 12; intNumInYear++)
            {
                dteJieQiDateTime = GetJieQi(Gregorian.Year, intNumInYear * 2);
                DateTime dteFirstDayOfMonth = new DateTime(
                    dteJieQiDateTime.Year,
                    dteJieQiDateTime.Month,
                    dteJieQiDateTime.Day);
                if (Gregorian < dteFirstDayOfMonth)
                {
                    break;
                }
            }
            //返回0-60 0是上一年的最后一个月
            return ((Gregorian.Year - 1899) % 5 * 12 + intNumInYear);
        }

        #endregion

        #region 日柱 没有静态方法
        /// <summary>
        /// 获取日柱天干
        /// </summary>
        /// <returns>日柱天干</returns>
        public string GetLunarDayTianGan()
        {
            return TianGan[GetLunarDayNum() % 10];
        }

        /// <summary>
        /// 获取日柱地支
        /// </summary>
        /// <returns>日柱地支</returns>
        public string GetLunarDayDiZhi()
        {
            return DiZhi[GetLunarDayNum() % 12];
        }

        /// <summary>
        /// 日柱天干地支计算方法
        /// </summary>
        /// <returns>日序号 0-60</returns>
        private int GetLunarDayNum()
        {
            DateTime dteFirst = new DateTime(1900, 1, 1);
            TimeSpan ts = Gregorian - dteFirst;
            return (ts.Days + 10) % 60;
        }
        #endregion

        #region 农历日月

        /// <summary>
        /// 获取农历月份名称
        /// </summary>
        /// <returns>农历月份名称</returns>
        public string GetLunarMonthName()
        {
            return GetLunarMonthName(m_dtelunar.m_intMonth, m_dtelunar.m_intLeapNum);
        }

        /// <summary>
        /// 获取农历月份名称
        /// </summary>
        /// <param name="month">指定月份 1-13</param>
        /// <param name="leapMonth">闰月所在月</param>
        /// <returns>农历月份名称</returns>
        public static string GetLunarMonthName(int month, int leapMonth = 0)
        {
            string strReturnValue = "";
            if (month == leapMonth) strReturnValue += "润";
            if (leapMonth != 0 && month >= leapMonth) month--;
            strReturnValue += MonthName[month];
            return strReturnValue;
        }

        /// <summary>
        /// 获取农历日名称
        /// </summary>
        /// <returns>农历日名称</returns>
        public string GetLunarDayName()
        {
            return GetLunarDayName(m_dtelunar.m_intDay);
        }
        /// <summary>
        /// 获取农历日名称
        /// </summary>
        /// <param name="day">指定日期序号 从1开始</param>
        /// <returns>农历日名称</returns>
        public static string GetLunarDayName(int day)
        {
            return DayName[day];
        }
        #endregion

        #region 星座
        /// <summary>
        /// 获取星座中文名
        /// </summary>
        /// <returns>星座中文名</returns>
        public string GetXingZuoCH()
        {
            return GetXingZuoCH(Gregorian.Month, Gregorian.Day);
        }

        /// <summary>
        /// 获取星座中文名
        /// </summary>
        /// <param name="month">月</param>
        /// <param name="day">日</param>
        /// <returns>星座中文名</returns>
        public static string GetXingZuoCH(int month, int day)
        {
            return XingZuoCH[GetXingZuoNum(month, day)];
        }

        /// <summary>
        /// 获取星座英文名
        /// </summary>
        /// <returns>星座英文名</returns>
        public string GetXingZuoEN()
        {
            return GetXingZuoEN(Gregorian.Month, Gregorian.Day);
        }
        /// <summary>
        /// 获取星座英文名
        /// </summary>
        /// <param name="month">月</param>
        /// <param name="day">日</param>
        /// <returns>星座英文名</returns>
        public static string GetXingZuoEN(int month, int day)
        {
            return XingZuoEN[GetXingZuoNum(month, day)];
        }

        /// <summary>
        /// 星座计算方法
        /// </summary>
        /// <param name="month">月</param>
        /// <param name="day">日</param>
        /// <returns>星座数组Index</returns>
        private static int GetXingZuoNum(int month, int day)
        {
            int intNum = month - 1;
            if (day < XingZuoDay[intNum])
            {
                intNum--;
                if (intNum < 0) intNum += XingZuoDay.Length;
            }
            return intNum;
        }
        #endregion

        /// <summary>
        /// 重写ToString()
        /// 作者:赵伯涛
        /// 2014-09-26 21:57
        /// </summary>
        /// <returns>返回值</returns>
        public override string ToString()
        {
            return string.Format("{0}年{1}月{2}日",
                this.GetLunarYearShengXiao(),
                this.GetLunarMonthName(),
                this.GetLunarDayName());
        }
    }
}
